`timescale 1ns / 1ps

module pwc_buffer
#(
    parameter N_IN      = 16,
    parameter N_OUT     = 16,
    parameter N_ICH     = 384,
    parameter N_OCH     = 512,
    parameter BIT       = 8,
    parameter RAM_TYPE  = "distributed",
    parameter RAM_MODE  = "read_first",
    parameter RAM_LATENCY = 1
)
(
    input   clk,
    input   rst,
    
    input   i_vld,
    output  i_rdy,
    input   [N_IN*BIT-1 : 0]    i_data,
    
    output  o_vld,
    input   o_rdy,
    output  o_last,
    output  [N_IN*BIT-1 : 0]    o_data
);

localparam FOLD_I = N_ICH / N_IN;
localparam FOLD_O = N_OCH / N_OUT;
localparam D_DEPTH = FOLD_I * 2;
localparam D_WIDTH = N_IN * BIT;
localparam A_WIDTH = $clog2(D_DEPTH);
localparam MEM_SIZE = D_DEPTH * D_WIDTH;

wire w_en;
wire [A_WIDTH-1 : 0] w_addr;
wire [D_WIDTH-1 : 0] w_data;
wire r_en;
wire [A_WIDTH-1 : 0] r_addr;
wire [D_WIDTH-1 : 0] r_data;

reg  [1 : 0] buffer_state;
wire [1 : 0] buffer_clr;
wire [1 : 0] buffer_set;

always @(posedge clk)
begin
    if (rst)
        buffer_state <= 2'b0;
    else
    begin
        if (buffer_clr[0])
            buffer_state[0] <= 1'b0;
        else if (buffer_set[0])
            buffer_state[0] <= 1'b1;
        
        if (buffer_clr[1])
            buffer_state[1] <= 1'b0;
        else if (buffer_set[1])
            buffer_state[1] <= 1'b1;
    end
end

pwc_buffer_writer #(
    .FOLD_I    (FOLD_I),
    .D_WIDTH   (D_WIDTH),
    .A_WIDTH   (A_WIDTH)
) inst_writer
(
    .clk            (clk),
    .rst            (rst),
    
    .i_vld          (i_vld),
    .i_rdy          (i_rdy),
    .i_data         (i_data),

    .bstate         (buffer_state),
    .bstate_set     (buffer_set),
    
    .w_en           (w_en),
    .w_addr         (w_addr),
    .w_data         (w_data)
);

pwc_buffer_reader #(
    .FOLD_I         (FOLD_I),
    .FOLD_O         (FOLD_O),
    .D_WIDTH        (D_WIDTH),
    .A_WIDTH        (A_WIDTH),
    .RAM_LATENCY    (RAM_LATENCY)
) inst_reader
(
    .clk            (clk),
    .rst            (rst),
    
    .o_vld          (o_vld),
    .o_rdy          (o_rdy),
    .o_last         (o_last),
    .o_data         (o_data),

    .bstate         (buffer_state),
    .bstate_clr     (buffer_clr),
    
    .r_en           (r_en),
    .r_addr         (r_addr),
    .r_data         (r_data)
);

xpm_memory_sdpram #(
   .ADDR_WIDTH_A            (A_WIDTH),                  // DECIMAL
   .ADDR_WIDTH_B            (A_WIDTH),                  // DECIMAL
   .AUTO_SLEEP_TIME         (0),                        // DECIMAL
   .BYTE_WRITE_WIDTH_A      (D_WIDTH),                  // DECIMAL
   .CLOCKING_MODE           ("common_clock"),           // String
   .ECC_MODE                ("no_ecc"),                 // String
   .MEMORY_INIT_FILE        ("none"),                   // String
   .MEMORY_INIT_PARAM       ("0"),                      // String
   .MEMORY_OPTIMIZATION     ("true"),                   // String
   .MEMORY_PRIMITIVE        (RAM_TYPE),                 // String
   .MEMORY_SIZE             (MEM_SIZE),                 // DECIMAL
   .MESSAGE_CONTROL         (1),                        // DECIMAL
   .READ_DATA_WIDTH_B       (D_WIDTH),                  // DECIMAL
   .READ_LATENCY_B          (RAM_LATENCY),              // DECIMAL
   .READ_RESET_VALUE_B      ("0"),                      // String
   .USE_EMBEDDED_CONSTRAINT (0),                        // DECIMAL
   .USE_MEM_INIT            (1),                        // DECIMAL
   .WAKEUP_TIME             ("disable_sleep"),          // String
   .WRITE_DATA_WIDTH_A      (D_WIDTH),                  // DECIMAL
   .WRITE_MODE_B            (RAM_MODE)                  // String
)
inst_xpm_sdpram (
   .dbiterrb(),                     // 1-bit output: Status signal to indicate double bit error occurrence
                                    // on the data output of port B.

   .doutb(r_data),                  // READ_DATA_WIDTH_B-bit output: Data output for port B read operations.
   .sbiterrb(),                     // 1-bit output: Status signal to indicate single bit error occurrence
                                    // on the data output of port B.

   .addra(w_addr),                  // ADDR_WIDTH_A-bit input: Address for port A write operations.
   .addrb(r_addr),                  // ADDR_WIDTH_B-bit input: Address for port B read operations.
   .clka(clk),                      // 1-bit input: Clock signal for port A. Also clocks port B when
                                    // parameter CLOCKING_MODE is "common_clock".

   .clkb(1'b0),                     // 1-bit input: Clock signal for port B when parameter CLOCKING_MODE is
                                    // "independent_clock". Unused when parameter CLOCKING_MODE is
                                    // "common_clock".

   .dina(w_data),                   // WRITE_DATA_WIDTH_A-bit input: Data input for port A write operations.
   .ena(w_en),                      // 1-bit input: Memory enable signal for port A. Must be high on clock
                                    // cycles when write operations are initiated. Pipelined internally.

   .enb(r_en),                      // 1-bit input: Memory enable signal for port B. Must be high on clock
                                    // cycles when read operations are initiated. Pipelined internally.

   .injectdbiterra(1'b0),           // 1-bit input: Controls double bit error injection on input data when
                                    // ECC enabled (Error injection capability is not available in
                                    // "decode_only" mode).

   .injectsbiterra(1'b0),           // 1-bit input: Controls single bit error injection on input data when
                                    // ECC enabled (Error injection capability is not available in
                                    // "decode_only" mode).

   .regceb(1'b1),                   // 1-bit input: Clock Enable for the last register stage on the output
                                    // data path.

   .rstb(rst),                      // 1-bit input: Reset signal for the final port B output register stage.
                                    // Synchronously resets output port doutb to the value specified by
                                    // parameter READ_RESET_VALUE_B.

   .sleep(1'b0),                    // 1-bit input: sleep signal to enable the dynamic power saving feature.
   .wea(1'b1)                       // WRITE_DATA_WIDTH_A-bit input: Write enable vector for port A input
                                    // data port dina. 1 bit wide when word-wide writes are used. In
                                    // byte-wide write configurations, each bit controls the writing one
                                    // byte of dina to address addra. For example, to synchronously write
                                    // only bits [15-8] of dina when WRITE_DATA_WIDTH_A is 32, wea would be
                                    // 4'b0010.
);

endmodule

module pwc_buffer_writer
#(
    parameter FOLD_I    = 384 / 16,
    parameter D_WIDTH   = 16 * 8,
    parameter A_WIDTH   = 6
)
(
    input   clk,
    input   rst,
    
    input   i_vld,
    output  i_rdy,
    input   [D_WIDTH-1 : 0]     i_data,

    input   [1 : 0]             bstate,
    output  [1 : 0]             bstate_set,
    
    output  w_en,
    output  [A_WIDTH-1 : 0]     w_addr,
    output  [D_WIDTH-1 : 0]     w_data
);

localparam S_WT_0 = 2'b01;
localparam S_WT_1 = 2'b10;
localparam [A_WIDTH-1: 0] ZEROS = {A_WIDTH{1'b0}};
localparam [A_WIDTH-1: 0] ADDR_MAX_0 = 1 * FOLD_I - 1;
localparam [A_WIDTH-1: 0] ADDR_MAX_1 = 2 * FOLD_I - 1;

reg  [1 : 0] cstate;
reg  [A_WIDTH-1 : 0] c_addr;

reg  [1 : 0] nstate;
reg  [1 : 0] bs_set;
reg  enable;
reg  ii_rdy;
wire [1 : 0] fi_last;

assign i_rdy = ii_rdy;
assign bstate_set = bs_set;
assign w_en = enable;
assign w_addr = c_addr;
assign w_data = i_data;
assign fi_last[0] = c_addr == ADDR_MAX_0;
assign fi_last[1] = c_addr == ADDR_MAX_1;

always @(posedge clk)
begin
    if (rst)
        c_addr <= ZEROS;
    else
    begin
        if (enable)
        begin
            if (fi_last[1])
                c_addr <= ZEROS;
            else
                c_addr <= c_addr + 1'b1;
        end
    end
end

always @(posedge clk)
begin
    if (rst)
        cstate <= S_WT_0;
    else
        cstate <= nstate;
end

always @(*)
begin
    (* full_case *)
    case (cstate)
		S_WT_0:
        begin
            if (~bstate[0] & i_vld & fi_last[0])
                nstate = S_WT_1;
            else
                nstate = S_WT_0;
		end
		S_WT_1:
        begin
            if (~bstate[1] & i_vld & fi_last[1])
                nstate = S_WT_0;
            else
                nstate = S_WT_1;
		end
		default:
        begin
			nstate = S_WT_0;
		end
	endcase
end

always @(*)
begin
    (* full_case *)
    case (cstate)
		S_WT_0:
        begin
            ii_rdy = ~bstate[0];
            enable = ~bstate[0] & i_vld;
            bs_set[0] = ~bstate[0] & i_vld & fi_last[0];
            bs_set[1] = 1'b0;
		end
		S_WT_1:
        begin
            ii_rdy = ~bstate[1];
            enable = ~bstate[1] & i_vld;
            bs_set[0] = 1'b0;
            bs_set[1] = ~bstate[1] & i_vld & fi_last[1];
		end
	endcase
end

endmodule

// Bugful when RAM_LATENCY > 1
module pwc_buffer_reader
#(
    parameter FOLD_I    = 384 / 16,
    parameter FOLD_O    = 512 / 16,
    parameter D_WIDTH   = 16 * 8,
    parameter A_WIDTH   = 6,
    parameter RAM_LATENCY = 1
)
(
    input   clk,
    input   rst,
    
    input   o_rdy,
    output  o_vld,
    output  o_last,
    output  [D_WIDTH-1 : 0]     o_data,

    input   [1 : 0]             bstate,
    output  [1 : 0]             bstate_clr,
    
    output  r_en,
    output  [A_WIDTH-1 : 0]     r_addr,
    input   [D_WIDTH-1 : 0]     r_data
);

localparam S_RD_0 = 2'b01;
localparam S_RD_1 = 2'b10;
localparam [A_WIDTH-1: 0] ZEROS = {A_WIDTH{1'b0}};
localparam [A_WIDTH-1: 0] ADDR_MID = FOLD_I; 
localparam [A_WIDTH-1: 0] ADDR_MAX_0 = 1 * FOLD_I - 1;
localparam [A_WIDTH-1: 0] ADDR_MAX_1 = 2 * FOLD_I - 1;

reg  [1 : 0] cstate;
reg  [A_WIDTH-1 : 0] c_addr;

reg  [1 : 0] nstate;
reg  [1 : 0] bs_clr;
reg  enable;
reg  oo_vld;
reg  oo_last;

wire         oo_rdy;
wire [1 : 0] fi_last;
wire         fo_last;
wire         fo_en;
wire [1 : 0] fio_lst;

assign bstate_clr = bs_clr;
assign r_en = enable;
assign r_addr = c_addr;
assign o_data = r_data;

assign oo_rdy = ~o_vld | o_rdy;
assign fi_last[0] = c_addr == ADDR_MAX_0;
assign fi_last[1] = c_addr == ADDR_MAX_1;
assign fo_en = enable & (fi_last[0] | fi_last[1]);
assign fio_lst[0] = fi_last[0] & fo_last;
assign fio_lst[1] = fi_last[1] & fo_last;

always @(posedge clk)
begin
    if (rst)
        c_addr <= ZEROS;
    else
    begin
        if (enable)
        begin
            if (fi_last[0])
                c_addr <= fo_last ? ADDR_MID : ZEROS;
            else if (fi_last[1])
                c_addr <= fo_last ? ZEROS : ADDR_MID;
            else
                c_addr <= c_addr + 1'b1;
        end
    end
end

zq_counter #(
    .N  (FOLD_O)
) inst_fo
(
    .clk    (clk),
    .rst    (rst),
    .clken  (fo_en),
    .last   (fo_last),
    .out    ()
);

shift_reg_rst #(
    .N          (1),
    .WIDTH      (2)
) inst_vld_reg
(
    .clk        (clk),
    .rst        (rst),
    .clken      (oo_rdy),
    .i_data     ({oo_vld, oo_last}),
    .o_data     ({ o_vld,  o_last})
);

always @(posedge clk)
begin
    if (rst)
        cstate <= S_RD_0;
    else
        cstate <= nstate;
end

always @(*)
begin
    (* full_case *)
    case (cstate)
		S_RD_0:
        begin
            if (bstate[0] & oo_rdy & fio_lst[0])
                nstate = S_RD_1;
            else
                nstate = S_RD_0;
		end
		S_RD_1:
        begin
            if (bstate[1] & oo_rdy & fio_lst[1])
                nstate = S_RD_0;
            else
                nstate = S_RD_1;
		end
		default:
        begin
			nstate = S_RD_0;
		end
	endcase
end

always @(*)
begin
    (* full_case *)
    case (cstate)
		S_RD_0:
        begin
            oo_vld = bstate[0];
            enable = bstate[0] & oo_rdy;
            oo_last = bstate[0] & fi_last[0];
            bs_clr[0] = bstate[0] & oo_rdy & fio_lst[0];
            bs_clr[1] = 1'b0;
		end
		S_RD_1:
        begin
            oo_vld = bstate[1];
            enable = bstate[1] & oo_rdy;
            oo_last = bstate[1] & fi_last[1];
            bs_clr[0] = 1'b0;
            bs_clr[1] = bstate[1] & oo_rdy & fio_lst[1];
		end
	endcase
end

endmodule
